package la.iok.hzsvn.lewin.movie.operation.service;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import la.iok.hzsvn.lewin.movie.core.RoleEnum;
import la.iok.hzsvn.lewin.movie.core.Token;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.core.model.RoomVo;
import la.iok.hzsvn.lewin.movie.operation.entity.ChargeOrder;
import la.iok.hzsvn.lewin.movie.operation.entity.FinancialAccount;
import la.iok.hzsvn.lewin.movie.operation.mapper.ChargeOrderMapper;
import la.iok.hzsvn.lewin.movie.operation.model.ChargeOrderInfo;
import la.iok.hzsvn.lewin.movie.operation.model.ChargeOrderParam;
import la.iok.hzsvn.lewin.movie.operation.model.ChargeOrderVo;
import la.iok.hzsvn.lewin.movie.operation.model.ChargePrepareRes;
import la.iok.hzsvn.lewin.movie.services.MovieServiceInvoker;
import la.iok.hzsvn.lewin.mybatis.model.PageVo;
import la.iok.hzsvn.lewin.mybatis.service.BaseServiceImpl;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.annotations.NotNullElement;
import la.iok.hzsvn.share.annotations.Nullable;
import la.iok.hzsvn.share.utils.CollectionUtils;
import la.iok.hzsvn.share.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ChargeOrderServiceImpl extends BaseServiceImpl<ChargeOrder, ChargeOrderMapper,Long> implements ChargeOrderService{
    private static final Logger logger = LoggerFactory.getLogger(ChargeOrderService.class);
    private static final String[] ORDER_COLUMNS = new String[]{};
    private final FinancialAccountService financialAccountService;
    private final PayService wxPayService;
    private final PayService aliPayService;
    private final MovieServiceInvoker movieServiceInvoker;

    public ChargeOrderServiceImpl(ChargeOrderMapper mapper,
                                  FinancialAccountService financialAccountService,
                                  @Qualifier("wxPayService") PayService wxPayService,
                                  @Qualifier("aliPayService") PayService aliPayService,
                                  MovieServiceInvoker movieServiceInvoker) {
        super(mapper);
        this.financialAccountService = financialAccountService;
        this.wxPayService = wxPayService;
        this.aliPayService = aliPayService;
        this.movieServiceInvoker = movieServiceInvoker;
    }

    @Transactional
    @Override
    public ChargePrepareRes save(@NotNull Token token, @NotNull ChargeOrderParam param) {
        ErrorCode.PROPERTY_CHECK_ERROR.assertNotNull(param.getAmount(),"金额","金额不能为空");
        ErrorCode.PROPERTY_CHECK_ERROR.assertNotNull(param.getRoomId(),"房间id","房间id不能为空");
        ErrorCode.PROPERTY_CHECK_ERROR.assertTrue(param.getRoomId() > 0,"房间id","房间id必须大于0");
        ErrorCode.PROPERTY_CHECK_ERROR.assertAnyOf(param.getPayType(), new Integer[]{ChargeOrder.PAY_TYPE_WX,
                ChargeOrder.PAY_TYPE_ALIPAY,ChargeOrder.PAY_TYPE_ADMIN},"支付","不支持的支付方式");
        if(param.payTypeAdmin()){
            ErrorCode.PROPERTY_CHECK_ERROR.assertTrue(param.getAmount() != 0,"金额","金额不能为0");
            token.validAnyAuthority(RoleEnum.Admin);
        }else{
            ErrorCode.PROPERTY_CHECK_ERROR.assertTrue(param.getAmount() > 0,"金额","金额必须大于0");
            token.validAnyAuthority(RoleEnum.Admin,RoleEnum.Manager,RoleEnum.ShopManager);
        }
        RoomVo room = movieServiceInvoker.roomInfo(param.getRoomId());
        ErrorCode.ROOM_NOT_EXIST.assertNotNull(room,param.getRoomId());
        validate(token, room.getOrgUnitId());
        ChargeOrder order = new ChargeOrder();
        order.setRoomId(param.getRoomId());
        order.setRoomName(room.getName());
        order.setAmount(param.getAmount());
        order.setState(ChargeOrder.STATE_PAYING);
        order.setOrgUnitId(room.getOrgUnitId());
        order.setOrgUnitName(room.getOrgUnitName());
        order.setPrepareDate(new Date());
        order.setUserId(token.getUserId());
        order.setPayType(param.getPayType());
        order.setUuid(UUID.randomUUID().toString().replace("-",""));
        order.setUsername(token.getUsername());
        insertSelective(order);
        if(param.payTypeAdmin()){
            order.setState(ChargeOrder.STATE_PAID);
            order.setEndTime(new Date());
            mapper.update(order);
            financialAccountService.charge(order.getRoomId(), order.getAmount());
            FinancialAccount account = financialAccountService.account(order.getRoomId());
            update(order.getId(),"accountRemained",account.getBalance());
            return new ChargePrepareRes(order.getId(),"");
        }else{
            String codeUrl = null;
            try{
                if(param.payTypeAli()){
                    codeUrl = aliPayService.prepare(order);
                }else{
                    codeUrl = wxPayService.prepare(order);
                }

            }catch (Exception e){
                ErrorCode.PREPARE_ORDER_RES_ERROR.fail(codeUrl,e);
            }
            ErrorCode.PREPARE_ORDER_RES_ERROR.assertNotBlank(codeUrl,codeUrl);
            return new ChargePrepareRes(order.getId(),codeUrl);
        }
    }

    @Transactional
    @Override
    public void close(@NotNull Token token,@Nullable Long id) {
        token.validAnyAuthority(RoleEnum.Admin,RoleEnum.Manager,RoleEnum.ShopManager);
        ChargeOrder order = select(id);
        ErrorCode.RECORD_NOT_EXIST.assertNotNull(order,id);
        validate(token,order.getOrgUnitId());
        //只有支付中的订单才能关闭
        if(order.paying()){
            //关单之前先从微信确认一下是未支付才关闭
            if(order.payTypeAli()){
                aliPayService.queryOrder(order);
            }else{
                wxPayService.queryOrder(order);
            }
            if(order.paying()){
                if(order.payTypeAli()){
                    aliPayService.closeOrder(order);
                }else{
                    wxPayService.closeOrder(order);
                }
                order.setState(ChargeOrder.STATE_CLOSED);
                order.setEndTime(new Date());
                int cnt = mapper.update(order);
                if(cnt == 1){
                    logger.info("成功关闭订单:id={}",order.getId());
                }else{
                    logger.error("支付平台关单成功,本地更新状态失败:id={}",id);
                }
            }else{
                //可能支付成功了，但是还没有收到通知,那更新订单状态
                int cnt = mapper.update(order);
                if(cnt == 1){
                    logger.info("成功更新订单:id={}",order.getId());
                }else{
                    logger.error("支付平台订单状态:{},本地更新状态失败:id={}",order.getState(),id);
                }
            }
        }
    }

    @Transactional
    @Override
    public void update(@NotNull ChargeOrderInfo order) {
        if(order.success() || order.closed()){
            ChargeOrder entity = select("uuid",order.orderId());
            if(entity != null && entity.paying()){
                entity.setState(order.getState());
                entity.setEndTime(order.getEndTime()==null?new Date():order.getEndTime());
                int cnt = mapper.update(entity);
                if(cnt == 1){
                    logger.info("更新订单状态成功:更新后状态:{},id:{}",entity.getState(),entity.getId());
                    financialAccountService.charge(entity.getRoomId(),entity.getAmount());
                    FinancialAccount account = financialAccountService.account(entity.getRoomId());
                    update(entity.getId(),"accountRemained",account.getBalance());
                }else{
                    logger.error("更新订单状态失败:订单状态:{},uuid:{},endTime:{}",order.getState(),order.orderId(),order.getEndTime());
                }
            }else{
                logger.error("订单[{}]不存在",order.orderId());
            }
        }
    }

    @Transactional
    @Override
    public ChargeOrderVo queryWithUpdate(Token token, Long id) {
        token.validAnyAuthority(RoleEnum.Admin,RoleEnum.Manager,RoleEnum.ShopManager);
        ChargeOrder order = select(id);
        ErrorCode.RECORD_NOT_EXIST.assertNotNull(order,id);
        validate(token,order.getOrgUnitId());
        if(order.paying()){
            ChargeOrder param = new ChargeOrder();
            param.setUuid(order.getUuid());
            param.setState(ChargeOrder.STATE_PAYING);
            if(order.payTypeAli()){
                aliPayService.queryOrder(param);
            }else{
                wxPayService.queryOrder(param);
            }
            if(!param.paying()){
                update(param);
                order = select(id);
            }
        }
        return po2vo(order);
    }

    private ChargeOrderVo po2vo(ChargeOrder order) {
        ChargeOrderVo vo = new ChargeOrderVo();
        BeanUtils.copyProperties(order,vo);
        return vo;
    }

    @NotNull
    @NotNullElement
    private List<ChargeOrderVo> po2vo(@Nullable Collection<ChargeOrder> orders){
        if(orders == null){
            return Collections.emptyList();
        }
        return orders.stream().filter(Objects::nonNull).map(this::po2vo).collect(Collectors.toList());
    }

    @Override
    public PageVo<ChargeOrderVo> page(Token token, int page, int limit, String orderColumn, String order, Long orgUnitId,Long roomId) {
        orderColumn = StringUtils.isBlank(orderColumn)?"state":orderColumn;
        order = StringUtils.isBlank(order)?"ascending":order;
        order = "ascending".equalsIgnoreCase(order)?"ASC":"DESC";
        List<Long> orgUnits = null;
        Long userId = null;
        if(token.hasAuthority(RoleEnum.Admin)){
            orgUnits = new ArrayList<>();
            if(orgUnitId != null){
                orgUnits.add(orgUnitId);
            }
        }else{
            if(token.hasAnyAuthority(RoleEnum.Manager,RoleEnum.ShopManager)){
                orgUnits = movieServiceInvoker.childrenOrgUnitIds(token.getOrgUnitId());
                orgUnits = orgUnits == null?new ArrayList<>():new ArrayList<>(orgUnits);
                if(orgUnitId != null){
                    if(Objects.equals(token.getOrgUnitId(),orgUnitId)){
                        orgUnits.add(orgUnitId);
                    }else if(!orgUnits.contains(orgUnitId)){
                        orgUnits.clear();
                    }else{
                        orgUnits = Collections.singletonList(orgUnitId);
                    }
                }else{
                    if(token.getOrgUnitId() != null){
                        orgUnits.add(token.getOrgUnitId());
                    }
                }
                if(!token.hasAuthority(RoleEnum.Manager)){
                    userId = token.getUserId();
                }
            }
            if(CollectionUtils.isEmpty(orgUnits)){
                return new PageVo<>(limit,page);
            }
        }

        Page<ChargeOrder> list = PageHelper.startPage(page,limit);
        mapper.list(orderColumn,order,orgUnits,roomId,userId);
        PageVo<ChargeOrderVo> pageVo = new PageVo<>();
        pageVo.setPages(list.getPages());
        pageVo.setTotal(list.getTotal());
        pageVo.setCurrent(list.getPageNum());
        pageVo.setSize(list.getPageSize());
        pageVo.setRecords(po2vo(list));
        return pageVo;
    }

    @Override
    public int total(@NotNull Token token, @NotNull Date start, @NotNull Date end) {
        Integer total;
        if(token.hasAuthority(RoleEnum.Admin)){
            total = mapper.total(start,end,null);
        }else{
            List<Long> list = movieServiceInvoker.childrenOrgUnitIds(token.getOrgUnitId());
            if(list.isEmpty()){
                total = mapper.total(start,end,Collections.singletonList(token.getOrgUnitId()));
            }else{
                total = mapper.total(start,end,list);
            }
        }
        return total != null ? total : 0;
    }

    private void validate(Token token, Long orgUnitId) {
        if(token.hasAuthority(RoleEnum.Admin)){

        }else if(token.hasAuthority(RoleEnum.Manager)){
            if(!Objects.equals(orgUnitId, token.getOrgUnitId())){
                List<Long> list = movieServiceInvoker.childrenOrgUnitIds(token.getOrgUnitId());
                ErrorCode.ORG_UNIT_ACCESS_DENIED.assertTrue(list.contains(orgUnitId), orgUnitId);
            }
        }else{
            ErrorCode.ORG_UNIT_ACCESS_DENIED.assertEqual(token.getOrgUnitId(), orgUnitId, orgUnitId);
        }
    }
}
